package org.packtracker.client;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;

public class PackTracker implements EntryPoint {

	/**
	 * Create a remote service proxies to talk to the server-side services
	 */
	private final PersistenceServiceAsync persistenceService = GWT
			.create(PersistenceService.class);

	// Global variables
	private static final int REFRESH_INTERVAL = 30000; //ms
	private boolean showOnlyOwnedPackages = true;
	private LoginInfo loginInfo = null;
	private ArrayList<Package> packages = new ArrayList<Package>();

	//LOGIN
	private HorizontalPanel loginPanel = new HorizontalPanel();
	private Anchor signInLink = new Anchor("Sign In");
	private Anchor signOutLink = new Anchor("Sign Out");
	private Label loginLabel = new Label();
	
	// TOP
	private Label topTitleLabel = new Label();
	private Label topSubtitleLabel = new Label();
	private VerticalPanel topWelcomePanel = new VerticalPanel();
	private Label topWelcomeTitleLabel = new Label();
	private HTML topWelcomeHtml = new HTML();
	private VerticalPanel topPanel = new VerticalPanel();
	
	// CENTER
	private VerticalPanel mainPanel = new VerticalPanel();
	private Label infoLabel = new Label();
	private Button switchFilterButton = new Button(
			"Show me everybody's packages");
	private FlexTable packagesFlexTable = new FlexTable();
	
	private HorizontalPanel addPanel = new HorizontalPanel();
	private Label addPackageLabel = new Label();
	private FlexTable addPackageTable = new FlexTable();
	private Label trackingIdLabel = new Label();
	private TextBox trackingIdTextBox = new TextBox();
	private Label trackerLabel = new Label();
	private ListBox trackerListBox = new ListBox();
	private Button addPackageButton = new Button();
	private Label sendMailLabel = new Label();
	private CheckBox mailCheckBox = new CheckBox();
	private Label sendSmsLabel = new Label();
	private Label mobileNumberLabel = new Label();
	private TextBox mobileNumberTextBox = new TextBox();

	// BOTTOM
	private Label lastCheckedLabel = new Label();
	private HTML contactHtml = new HTML();
	
	/**
	 * Entry point method.
	 */
	public void onModuleLoad() {

		// Check login status using login service.
		LoginServiceAsync loginService = GWT.create(LoginService.class);
		loginService.login(GWT.getHostPageBaseURL(),
				new AsyncCallback<LoginInfo>() {
					public void onFailure(Throwable error) {
					}

					public void onSuccess(LoginInfo result) {
						loginInfo = result;
						loginLabel.setText("Signed in as "
								+ loginInfo.getNickname());

						if (loginInfo.isLoggedIn()) {
							loadPackTracker();
						} else {
							loadLogin();
						}
					}
				});
	}

	private void loadLogin() {
		// Associate the Main panel with the HTML host page.
		RootPanel.get("packageList").add(mainPanel);
		
		// Assemble login panel.
		signInLink.setHref(loginInfo.getLoginUrl());
		loginLabel.setText("You are not signed in");
		loginLabel.setStyleName("login-label");
		loginPanel.add(loginLabel);
		loginPanel.add(signInLink);
		loginPanel.setStyleName("login-panel");
		
		//Assemble top panel
		assembleTopPanel();

		//Assemble info panel
		infoLabel
				.setText("Please sign with your Google account to use the PackTracker application");
		infoLabel.setStyleName("yellowinfolabel");
		
		//Assemble main panel
		mainPanel.add(loginPanel);
		mainPanel.add(topPanel);
		mainPanel.add(infoLabel);
	}

	private void loadPackTracker() {
		// Associate the Main panel with the HTML host page.
		RootPanel.get("packageList").add(mainPanel);

		// Set up sign out hyperlink.
		signOutLink.setHref(loginInfo.getLogoutUrl());

		// Assemble login info panel
		loginPanel.setStylePrimaryName("login-panel");
		loginPanel.add(loginLabel);
		loginPanel.add(signOutLink);
		
		//Assemble top panel
		assembleTopPanel();
		
		// Create table for package data.
		packagesFlexTable.setVisible(false);
		packagesFlexTable.setText(0, 0, "Owner");
		packagesFlexTable.setText(0, 1, "Tracker");
		packagesFlexTable.setText(0, 2, "Tracking ID");
		packagesFlexTable.setText(0, 3, "Status");
		packagesFlexTable.setText(0, 4, "Remove");
		packagesFlexTable.getRowFormatter().addStyleName(0, "tableheader");

		// Assemble Add Package Table panel.
		
		trackingIdLabel.setText("Tracking ID:");
		trackingIdLabel.setStyleName("input-label");
		trackerLabel.setText("Tracker:");
		trackerLabel.setStyleName("input-label");
		sendMailLabel.setText("Mail to my Gmail account:");
		sendMailLabel.setStyleName("input-label");
		sendSmsLabel.setText("SMS to this mobile number:");
		sendSmsLabel.setStyleName("input-label");
		
		AsyncCallback<List<String>> populateTrackerNamesCallback = new AsyncCallback<List<String>>() {
			public void onSuccess(List<String> trackerNames) {
				for (String name : trackerNames) {
					trackerListBox.addItem(name);
				}
			}
			public void onFailure(Throwable caught) {
				infoLabel.setText("Remote call for reading tracker names failed.");
				infoLabel.setStylePrimaryName("redinfolabel");
				infoLabel.setVisible(true);
			}
		};
		persistenceService.getTrackerNames(populateTrackerNamesCallback);
		mailCheckBox.setValue(true);
		mobileNumberTextBox.setEnabled(false);
		addPackageButton.setText("Add package");

		addPackageTable.setWidget(0, 0, trackingIdLabel);
		addPackageTable.setWidget(1, 0, trackerLabel);
		addPackageTable.setWidget(2, 0, sendMailLabel);
		addPackageTable.setWidget(3, 0, sendSmsLabel);
		addPackageTable.setWidget(0, 1, trackingIdTextBox);
		addPackageTable.setWidget(1, 1, trackerListBox);
		addPackageTable.setWidget(2, 1, mailCheckBox);
		addPackageTable.setWidget(3, 1, mobileNumberTextBox);
		addPackageTable.setWidget(4, 1, addPackageButton);
		addPackageTable.getColumnFormatter().setStyleName(0, "form-table-input-column");
		addPackageTable.setStyleName("form-table");
		
		addPanel.add(addPackageTable);
		addPanel.setStyleName("form-panel");

		// Assemble Main panel.
		mainPanel.add(loginPanel);
		mainPanel.add(topPanel);
		infoLabel.setVisible(false);
		mainPanel.add(switchFilterButton);
		packagesFlexTable.addStyleName("table");
		mainPanel.add(packagesFlexTable);
		mainPanel.add(infoLabel);
		mainPanel.add(addPanel);
		lastCheckedLabel.setStyleName("lastcheckedlabel");
		mainPanel.add(lastCheckedLabel);
		contactHtml.setHTML("<a href=\"mailto:ramon@arellano.com\">Contact us</a>");
		contactHtml.setStyleName("contact-text");
		mainPanel.add(contactHtml);

		// Move cursor focus to the input box.
		trackingIdTextBox.setFocus(true);

		// Setup timer to refresh list automatically.
		Timer refreshTimer = new Timer() {
			@Override
			public void run() {
				refreshLastCheckedDate();
			}
		};
		refreshTimer.scheduleRepeating(REFRESH_INTERVAL);

		// Listen for mouse events on the Add button.
		addPackageButton.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				addPackage();
			}
		});

		// Listen for keyboard events in the input box.
		trackingIdTextBox.addKeyPressHandler(new KeyPressHandler() {
			public void onKeyPress(KeyPressEvent event) {
				if (event.getCharCode() == KeyCodes.KEY_ENTER) {
					addPackage();
				}
			}
		});

		// Listen for mouse events on the Swithc Filter button.
		switchFilterButton.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				showOnlyOwnedPackages = !showOnlyOwnedPackages;
				if (showOnlyOwnedPackages) {
					switchFilterButton.setText("Show me everybody's packages");
				} else {
					switchFilterButton.setText("Show only my packages");
				}

				packagesFlexTable.setVisible(false);
				populateTable();
			}
		});

		// Populate table
		populateTable();

		refreshLastCheckedDate();
	}

	private void assembleTopPanel() {
		topTitleLabel.setText("PackTracker");
		topTitleLabel.setStyleName("top-title");
		topSubtitleLabel.setText("Easy and automatic package tracking for the restless");
		topSubtitleLabel.setStyleName("top-subtitle");
		topWelcomeTitleLabel.setText("Pack? Track? What?");
		topWelcomeTitleLabel.setStyleName("titled-panel-title");
		topWelcomeHtml.setHTML("<p>Hate having to check Hong Kong Post or other trackers " +
				"several times a day to see if your package has gotten any closer? " +
				"Register your package with us and we'll send you " +
				"an e-mail everytime your package status changes! (SMS warnings available soon!)</p><p>" +
				"We currently support <a href=\"hongkongpost.com\">Hong Kong Post</a> and " +
				"<a href=\"posten.no\">Posten</a> (Norway). Want us to add other trackers? " +
				"<a href=\"mailto:ramon@arellano.com\">Get in touch!</a>");
		topWelcomeHtml.setStyleName("titled-panel-text");
		topWelcomePanel.add(topWelcomeTitleLabel);
		topWelcomePanel.add(topWelcomeHtml);
		topWelcomePanel.setStyleName("titled-panel");
		
		topPanel.add(topTitleLabel);
		topPanel.add(topSubtitleLabel);
		topPanel.add(topWelcomePanel);
		topPanel.setStyleName("top-panel");
	}

	/**
	 * Add package to FlexTable. Executed when the user clicks the addButton or
	 * presses enter in the newPackageTextBox.
	 */
	private void addPackage() {
		final String trackingId = trackingIdTextBox.getText().toUpperCase()
				.trim();
		trackingIdTextBox.setFocus(true);
		final String tracker = trackerListBox.getItemText(trackerListBox
				.getSelectedIndex());

		// Tracking ID code must be between 1 and 20 chars that are numbers or
		// letters
		if (!trackingId.matches("^[0-9A-Z]{1,20}$")) {
			Window.alert("'" + trackingId + "' is not a valid tracking ID.");
			trackingIdTextBox.selectAll();
			return;
		}

		trackingIdTextBox.setText("");

		// TODO: Don't add the package if it's already in the table.
		// if (packages.contains(trackingId))
		// return;

		// Add the stock to the table.
		addPackage(trackingId, tracker);

	}

	private void refreshLastCheckedDate() {
		// Callback for readLastCheckedDate
		AsyncCallback<Date> callback = new AsyncCallback<Date>() {
			public void onFailure(Throwable caught) {
				// Show the RPC error message to the user
				infoLabel.setText("Remote call for reading last checked date failed.");
				infoLabel.setStylePrimaryName("redinfolabel");
				infoLabel.setVisible(true);
			}

			public void onSuccess(Date lastCheckedDate) {
				if (lastCheckedDate != null) {
					lastCheckedLabel.setText("Trackers last checked on "
							+ lastCheckedDate.toString());
				}
			}
		};

		persistenceService.readLastCheckedDate(callback);

	}

	private void populateTable() {

		// Callback for readPackages
		AsyncCallback<List<Package>> callback = new AsyncCallback<List<Package>>() {
			public void onFailure(Throwable caught) {
				// Show the RPC error message to the user
				infoLabel.setText("Remote call for readPackages failed.");
				infoLabel.setStyleName("redinfolabel");
				infoLabel.setVisible(true);

			}

			public void onSuccess(List<Package> myPackages) {
				packages = new ArrayList<Package>(myPackages);
				Iterator<Package> iter = packages.iterator();

				if (packages.size() < 1) {
					infoLabel
							.setText("Your are not tracking any packages at the moment.");
					infoLabel.setStyleName("yellowinfolabel");
					infoLabel.setVisible(true);
				} else {
					// Empty the table if necessary
					if (packagesFlexTable.getRowCount() > 0) {
						for (int rowCount = 1; rowCount < packagesFlexTable
								.getRowCount(); rowCount++) {
							packagesFlexTable.removeRow(rowCount);
						}
					}

					int row = 1;
					while (iter.hasNext()) {
						// Populate table
						Package myPackage = iter.next();

						// Only add packages to table if user has chosen to see
						// all packages, or package is owned by user
						if (!showOnlyOwnedPackages
								|| myPackage.getMailRecipient().equals(
										loginInfo.getEmailAddress())) {
							insertTableRow(myPackage, row);

							row++;
						}
					}

					infoLabel.setText("");
					infoLabel.setVisible(false);
				}
			}
		};

		// Fetch packages
		infoLabel.setText("Loading...");
		infoLabel.setStyleName("greeninfolabel");
		infoLabel.setVisible(true);
		persistenceService.readPackages(showOnlyOwnedPackages, callback);

	}

	private void insertTableRow(Package myPackage, int row) {
		final Package tempPackage = myPackage;

		if (row == 1) {
			// First row to be added, make sure table is visible
			packagesFlexTable.setVisible(true);
		}

		packagesFlexTable.getRowFormatter().addStyleName(row, "tablerow");

		packagesFlexTable.setText(row, 0, myPackage.getMailRecipient());
		packagesFlexTable.setText(row, 1, myPackage.getTracker());
		packagesFlexTable.setHTML(row, 2, "<a href='"
				+ myPackage.getTrackerUrl() + "'>" + myPackage.getTrackingId()
				+ "</a>");
		
		String status = myPackage.getLastStatus();
		if(status.length() > Constants.STATUS_MAX_LENGTH) {
			status = status.substring(0, Constants.STATUS_MAX_LENGTH) + "...";
		}
		packagesFlexTable.setText(row, 3, status);

		// Add a button to remove this stock from the table.
		Button removePackageButton = new Button("x");
		removePackageButton.addStyleDependentName("remove");

		// Add active button only if user owns package
		if (myPackage.getMailRecipient().equals(loginInfo.getEmailAddress())
				|| loginInfo.getEmailAddress().equals(Constants.ADMIN_EMAIL)) {
			removePackageButton.addClickHandler(new ClickHandler() {
				public void onClick(ClickEvent event) {

					int removedIndex = packages.indexOf(tempPackage);

					packages.remove(removedIndex);
					packagesFlexTable.removeRow(removedIndex + 1);

					removePackage(tempPackage.getId(), tempPackage
							.getTrackingId());
				}
			});
		} else {
			removePackageButton.setEnabled(false);
		}

		packagesFlexTable.setWidget(row, 4, removePackageButton);
	}

	private void addPackage(String myTrackingId, String tracker) {
		final String trackingId = myTrackingId;

		// Callback 
		AsyncCallback<Package> callback = new AsyncCallback<Package>() {
			public void onFailure(Throwable caught) {
				// Show the RPC error message to the user
				infoLabel
						.setText("Remote call for adding package failed. Try again later.");
				infoLabel.setStylePrimaryName("redinfolabel");
				infoLabel.setVisible(true);

			}

			public void onSuccess(Package newPackage) {
				packages.add(newPackage);
				int row = packagesFlexTable.getRowCount();
				insertTableRow(newPackage, row);

				if (newPackage.getLastStatus().equals(Constants.UNKNOWN_STATUS)) {
					infoLabel
							.setText("Your package with ID "
									+ trackingId
									+ " was added, but we weren't able to retrieve status from the tracker. Click the tracking ID to go the the tracker page.");
					infoLabel.setStylePrimaryName("yellowinfolabel");

				} else {
					infoLabel.setText("Your package with ID " + trackingId
							+ " was added successfully.");
					infoLabel.setStylePrimaryName("greeninfolabel");
				}

				infoLabel.setVisible(true);

			}
		};

		infoLabel.setText("Adding...");
		infoLabel.setStyleName("greeninfolabel");
		infoLabel.setVisible(true);
		persistenceService.addPackage(trackingId, tracker, callback);

	}

	private void removePackage(Long packageId, String myTrackingId) {
		final String trackingId = myTrackingId;

		// Callback 
		AsyncCallback<Void> callback = new AsyncCallback<Void>() {
			public void onFailure(Throwable caught) {
				// Show the RPC error message to the user
				infoLabel
						.setText("Remote call for removing package failed. Try again later.");
				infoLabel.setStylePrimaryName("redinfolabel");
				infoLabel.setVisible(true);

			}

			public void onSuccess(Void ignore) {
				infoLabel.setText("Package with id " + trackingId
						+ " was successfully removed.");
				infoLabel.setStylePrimaryName("greeninfolabel");
				infoLabel.setVisible(true);

			}
		};

		persistenceService.removePackage(packageId, callback);

	}

}